package cn.lingyangwl.agile.gateway.sign.core;

import cn.lingyangwl.agile.gateway.body.*;
import cn.lingyangwl.agile.gateway.config.*;
import cn.lingyangwl.agile.gateway.model.*;
import cn.lingyangwl.agile.gateway.service.*;
import cn.lingyangwl.agile.gateway.sign.*;
import cn.lingyangwl.agile.gateway.sign.model.*;
import cn.lingyangwl.agile.model.enums.*;
import cn.lingyangwl.framework.sign.*;
import cn.lingyangwl.framework.tool.core.*;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author shenguangyang
 * @see SignPayload
 */
@Slf4j
public abstract class AbstractApiSign implements IApiSign {

    @Resource
    protected MyGatewayProperties properties;
    @Resource
    protected AccessKeyService accessKeyService;

    protected static Map<SignModeEnum, SignProperties.ConfigSign> signDataMap;

    protected static Map<SignModeEnum, IApiSign> apiSignMap = new HashMap<>();

    @PostConstruct
    public void initAbstractApiSign() {
        apiSignMap.put(this.getMode(), this);
        if (Objects.nonNull(signDataMap)) {
            return;
        }
        synchronized (AbstractApiSign.class) {
            if (Objects.nonNull(signDataMap)) {
                return;
            }
            signDataMap = new HashMap<>();
            Map<SignModeEnum, List<SignProperties>> tempMap = properties.getSign().stream()
                    .collect(Collectors.groupingBy(SignProperties::getMode));
            for (Map.Entry<SignModeEnum, List<SignProperties>> entry : tempMap.entrySet()) {
                SignProperties.ConfigSign signData = signDataMap.getOrDefault(entry.getKey(), new SignProperties.ConfigSign());
                Set<String> includeUrls = entry.getValue().stream()
                        .flatMap(e -> e.getConfig().stream())
                        .flatMap(e -> e.getIncludeUrls().stream()).collect(Collectors.toSet());
                Set<String> excludeUrls = entry.getValue().stream()
                        .flatMap(e -> e.getConfig().stream())
                        .flatMap(e -> e.getExcludeUrls().stream()).collect(Collectors.toSet());

                signData.getIncludeUrls().addAll(includeUrls);
                signData.getExcludeUrls().addAll(excludeUrls);
                signDataMap.put(entry.getKey(), signData);
            }
        }
    }

    /**
     * 获取实例
     */
    public static IApiSign getInstance(SignContext context) {
        if (Objects.isNull(signDataMap) || signDataMap.isEmpty()) {
            return null;
        }
        GatewayContext gatewayContext = context.getGatewayContext();
        String path = gatewayContext.getPath();
        for (Map.Entry<SignModeEnum, SignProperties.ConfigSign> entry : signDataMap.entrySet()) {
            SignProperties.ConfigSign signData = entry.getValue();
            boolean ret = StringUtils.matches(path, new ArrayList<>(signData.getIncludeUrls())) &&
                    !StringUtils.matches(path, new ArrayList<>(signData.getExcludeUrls()));
            if (ret) {
                return apiSignMap.get(entry.getKey());
            }
        }
        return null;
    }

    protected Object buildSignPayload(SignContext context) {
        GatewayContext gatewayContext = context.getGatewayContext();
        ServerWebExchange exchange = gatewayContext.getExchange();
        String cacheBody = gatewayContext.getCacheBody();
        SignPayload signPayload = null;
        Object reqData = null;
        // 优先从请求头中获取
        String headSign = gatewayContext.getExchange().getRequest().getHeaders().getFirst(SignPayload.SIGN);
        if (StringUtils.isNotEmpty(headSign)) {
            signPayload = SignPayload.buildByMultiValueMapOfStringType(gatewayContext.getExchange().getRequest().getHeaders());
            context.setSignPayload(signPayload);
        }

        if (StringUtils.isNotEmpty(cacheBody)) {
            // 获取body数据
            Object parse = JSON.parse(cacheBody);
            if (parse instanceof JSONObject) {
                JSONObject jsonObject = (JSONObject) parse;

                signPayload = Objects.nonNull(signPayload) ? signPayload : SignPayload.buildByMapOfObjectType(jsonObject);
                context.setSignPayload(signPayload);
                reqData = jsonObject;
            } else {
                log.warn("未实现对请求body为数组的签名验证处理, path: {}", gatewayContext.getPath());
            }
        } else {
            // 请表单和url中获取
            MultiValueMap<String, String> formData = gatewayContext.getFormData();
            MultiValueMap<String, String> urlData = gatewayContext.getUrlData();
            MultiValueMap<String, String> allData = new LinkedMultiValueMap<>();
            allData.putAll(formData);
            allData.putAll(urlData);

            signPayload = Objects.nonNull(signPayload) ? signPayload : SignPayload.buildByMultiValueMapOfStringType(allData);
            context.setSignPayload(signPayload);

            reqData = allData;
        }

        if (Objects.isNull(signPayload)) {
            return null;
        }
        signPayload.check();

        AccessKeyResp accessKeyResp = accessKeyService.getAccessKeyBySecretId(signPayload.getSecretId(), gatewayContext);
        if (Objects.isNull(accessKeyResp) || BoolEnum.FALSE.getCode().equals(accessKeyResp.getStatus())) {
            throw new SignException("密钥不存在或者被禁用");
        }
        context.setAccessKeyResp(accessKeyResp);
        return reqData;
    }

    @Override
    public boolean isVerifySign(SignContext context) {
        return true;
    }

    @Override
    public void verifySign(SignContext context) {
        Object reqData = buildSignPayload(context);
        if (Objects.isNull(reqData)) {
            return;
        }
        SignPayload signPayload = context.getSignPayload();
        AccessKeyResp accessKeyResp = context.getAccessKeyResp();
        SignUtils.checkSign(signPayload, accessKeyResp.getSecretKey(), reqData);
    }

    @Override
    public boolean isGenRespSign(SignContext context) {
        return false;
    }
}
